import sys, getopt
import logging
import re
import traceback
from threading import Thread
from bson import ObjectId


class Step(object):
    def __init__(self):
        self.skip = False  # skip all check

    def __iter__(self):
        for attr in self.__dir__():
            if re.match(r"^_init_\d_|^_step_\d_", attr):
                yield getattr(self, attr)

    def run(self):
        for func in self:
            if self.skip:
                return
            func()


def get_flat_value(d0, k: str, default=None):
    temp_k_list = k.split('.')
    if isinstance(d0, dict):
        temp_d = d0.get(temp_k_list[0])
    else:
        temp_d = d0[int(temp_k_list[0])]

    if temp_d is not None:
        if len(temp_k_list) > 1 and (isinstance(temp_d, dict) or isinstance(temp_d, list)):
            return get_flat_value(temp_d, '.'.join(temp_k_list[1:]), default)
        return temp_d
    else:
        return default


def dis_number(num, portion):
    r = num % portion

    target = (num-r) / portion
    result_list = [target for _ in range(portion-1)]

    result_list.append(target+r)

    return result_list


def trans_interval(interval):
    """

    Args:
        interval:

    Returns: unit: microseconds

    """
    if isinstance(interval, dict):
        num = interval.get('num', 0)
        unit = interval.get('unit', '')
    else:
        num = interval.num
        unit = interval.unit

    if unit == 'd':
        pass
    elif unit == 'w':
        num = num * 7
    elif unit == 'month':
        num = 30 * num
    elif unit == 'm':
        return num * 60 * 1000

    return num * 24 * 3600 * 1000


def merge_feature(f0, f1):
    if len(f0) < len(f1):
        temp = f0
        f0 = f1
        f1 = temp

    new_feature = {}
    for k, v in f0.items():
        new_feature[k] = v or f1.get(k, False)

    return new_feature


def backup(backup_collections, logger, db, now):
    logger.info("Start to backup.")
    for collection_name in backup_collections:
        backup_name = f'{collection_name}_{now}'
        db[backup_name].insert_many([item for item in db[collection_name].find()])

    logger.info("Backup succeed!")


def rollback(backup_collections, logger, db, now):
    logger.info("Start to rollback")
    for collection_name in backup_collections:
        backup_name = f'{collection_name}_{now}'

        db.drop_collection(collection_name)
        db[collection_name].insert_many([item for item in db[backup_name].find()])
        db.drop_collection(backup_name)

    logger.info("Rollback succeed!")


def backup_warp(collection_tuple, logger, db, arrow_now):
    def wrapper(func):
        def inner_wrapper():
            try:
                backup(collection_tuple, logger, db, arrow_now)
                return func()
            except Exception as e:
                rollback(collection_tuple, logger, db, arrow_now)
                logger.exception(e)
                traceback.print_exc()
        return inner_wrapper
    return wrapper


class RescanTaskThread(Thread):
    def __init__(self, collection_job, monitor, config, task_type, job_id, logger, queue, ignore_check=False):
        self._collection = collection_job
        self._monitor = monitor
        self.config = config
        self.origin_task_type = task_type
        self.logger = logger
        self.queue = queue
        self.ignore_check = ignore_check

        if not isinstance(job_id, ObjectId):
            job_id = ObjectId(job_id)
        if task_type == 'securityEvent':
            task_type = 'security_event'

        self.task_type = task_type
        self.job_id = job_id

        super(RescanTaskThread, self).__init__()

    def run(self):
        request_args = {
            "immediateExec": False,
            "jobId": str(self.job_id),
            "enableMonitor": True,
            f"{self.origin_task_type}Settings": self.config
        }

        if self.task_type == 'asset':
            request_args["taskSettings"] = self.config

        if self.ignore_check:
            request_args['ignore_check'] = self.ignore_check

        for i in range(5):
            try:
                resp = self._monitor.post(f'/api/v2/jobs/{self.task_type}/schedule', request_args)
                if resp.status_code == 200:
                    break
            except Exception as e:
                logging.exception(e)
        self.queue.get()
        self.queue.task_done()


def get_args(args_dict: dict) -> dict:
    opts, _ = getopt.getopt(sys.argv[1:], ''.join([f"{item.replace('-', '')}:" for item in args_dict.keys()]))

    tmp_dict = {}
    for opt, v in opts:
        if k := args_dict.get(opt):
            tmp_dict[k] = v

    return tmp_dict
